﻿using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;

namespace Useful.GUI
{
    public interface IProgressForm : IDisposable
    {
        string CurrentProgressText { get; set; }
        void Show();
        void Close();
        TimeSpan GetElapsed();
    }

    public class ProgressForm : Form, IProgressForm
    {
        private readonly GuiHelper.ProcessActionDelegate progressAction;
        private readonly GuiHelper.ProcessExceptionDelegate processException;
        private readonly GuiHelper.ProcessActionDelegate2 cancelAction;
        private readonly object context;

        private readonly BackgroundWorker bw = new BackgroundWorker();
        private readonly Timer timer = new Timer();
        private DateTime begin;

        private readonly PictureBox pbImage;
        private readonly Label lSec;
        private readonly Label lText;
        private readonly Button bCancel;

        public Exception Error { get; private set; }

        public ProgressForm(GuiHelper.ProcessActionDelegate progressAction, GuiHelper.ProcessExceptionDelegate processException, string message)
            : this(progressAction, processException, message, false, null, null)
        {
        }

        public ProgressForm(GuiHelper.ProcessActionDelegate progressAction, GuiHelper.ProcessExceptionDelegate processException, string message, bool enableCancel, GuiHelper.ProcessActionDelegate2 cancelAction, object context)
        {
            this.progressAction = progressAction;
            this.processException = processException;
            this.cancelAction = cancelAction;
            this.context = context;

            this.FormBorderStyle = FormBorderStyle.FixedToolWindow;
            this.MaximizeBox = false;
            this.MinimizeBox = false;
            this.ShowIcon = false;
            this.ShowInTaskbar = false;
            this.StartPosition = FormStartPosition.CenterParent;
            this.Text = "Пожалуйста, подождите...  ";
            this.ControlBox = false;
            this.Padding = new Padding(8);
            //this.Width = 300;

            this.KeyPreview = true;
            this.KeyDown += ProgressForm_KeyDown;
            this.FormClosed += ProgressForm_FormClosed;
            this.Load += ProgressForm_Load;

            pbImage = new PictureBox
            {
                Dock = DockStyle.Left,
                SizeMode = PictureBoxSizeMode.AutoSize,
                Image = null,
                //BackColor = Color.Yellow
            };

            pbImage.Image = (Bitmap)GuiHelper.ResourceManager.GetObject("ajax_loader");

            lSec = new Label
            {
                Dock = DockStyle.Left,
                AutoSize = false,
                TextAlign = ContentAlignment.MiddleLeft,
                Text = "0",
            };
            lSec.Width = TextRenderer.MeasureText("  0000,0 сек.  ", lSec.Font).Width;


            lText = new Label
            {
                Dock = DockStyle.Fill,
                AutoSize = false,
                TextAlign = ContentAlignment.MiddleCenter,
                Text = message,
                Width = 100,
                BorderStyle = BorderStyle.FixedSingle,
                Visible = !String.IsNullOrEmpty(message)
            };

            bCancel = new Button
            {
                Text = "Прервать",
                AutoSize = false,
                Dock = DockStyle.Right,
                Width = 70,
                Enabled = enableCancel,
            };
            bCancel.Click += bCancel_Click;

            this.Controls.Add(lText);
            this.Controls.Add(lSec);
            this.Controls.Add(pbImage);
            this.Controls.Add(bCancel);

            timer.Interval = 100;
            timer.Tick += timer_Tick;

            bw.WorkerReportsProgress = false;
            bw.WorkerSupportsCancellation = true;
            bw.DoWork += bw_DoWork;
            bw.RunWorkerCompleted += bw_RunWorkerCompleted;
        }

        private void bCancel_Click(object sender, EventArgs e)
        {
            try
            {
                if (bw.IsBusy && bw.WorkerSupportsCancellation)
                {
                    bw.CancelAsync();

                    if (cancelAction != null)
                        cancelAction(context);
                }
            }
            catch (Exception ex)
            {
                MessageBoxHelper.ShowError(ex);
            }
        }

        private void ProgressForm_FormClosed(object sender, FormClosedEventArgs e)
        {
            try
            {
                timer.Stop();
            }
            catch (Exception ex)
            {
                MessageBoxHelper.ShowError(ex);
            }
        }

        private static void ProgressForm_KeyDown(object sender, KeyEventArgs e)
        {
            e.Handled = true;
        }

        public TimeSpan GetElapsed()
        {
            return DateTime.Now - begin;
        }

        private void timer_Tick(object sender, EventArgs e)
        {
            try
            {
                lSec.Text = "  " + GetElapsed().TotalSeconds.ToString("F1") + " сек.  ";
                lSec.Width = TextRenderer.MeasureText(lSec.Text + "", lSec.Font).Width;
            }
            catch (Exception ex)
            {
                MessageBoxHelper.ShowError(ex);
            }
        }

        private void ProgressForm_Load(object sender, EventArgs e)
        {
            try
            {
                SetFormSize();

                begin = DateTime.Now;
                timer.Start();

                if (progressAction != null)
                    bw.RunWorkerAsync();
            }
            catch (Exception ex)
            {
                MessageBoxHelper.ShowError(ex);
            }
        }

        private void SetFormSize()
        {
            int width = bCancel.Width + pbImage.Width + lSec.Width + (lText.Visible ? TextRenderer.MeasureText(lText.Text, lText.Font).Width : 0);
            int widthFormHeader = TextRenderer.MeasureText(Text, Font).Width;

            this.ClientSize = new Size((width >= widthFormHeader ? width : widthFormHeader) + Padding.Left + Padding.Right, pbImage.Image.Height + Padding.Top + Padding.Bottom);

            this.Left = (Owner != null ? Owner.Bounds : Screen.PrimaryScreen.Bounds).Width / 2 - this.Width / 2;
            this.Top = (Owner != null ? Owner.Bounds : Screen.PrimaryScreen.Bounds).Height / 2 - this.Height / 2;

            if (Owner != null)
            {
                this.Left += Owner.Left;
                this.Top += Owner.Top;
            }
        }

        private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            timer.Stop();
            Close();

            if (e.Error != null)
                throw new Exception(e.Error.Message, e.Error);

            object res = null;
            try
            {
                res = e.Result;
            }
            catch (InvalidOperationException ex)
            {
                res = ex;
            }

            if (e.Cancelled)
            {
                if (res is Exception)
                {
                    Error = (Exception)res;

                    if (processException != null)
                        processException("bw_RunWorkerCompleted", Error);
                    else if (e.Cancelled)
                        Error = new CanceledProgressGuiException(Error);
                }
                else
                {
                    Error = new CanceledProgressGuiException(null);
                }
            }
            else if (res is Exception)
            {
                Error = (Exception)res;

                if (processException != null)
                    processException("bw_RunWorkerCompleted", Error);
            }
        }

        private void bw_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker w = (BackgroundWorker)sender;

            try
            {
                progressAction();
            }
            catch (Exception ex)
            {
                e.Result = ex;
            }
            finally
            {
                if (w.CancellationPending)
                    e.Cancel = true;
            }
        }

        public string CurrentProgressText
        {
            get
            {
                try
                {
                    return lText.Text;
                }
                catch (Exception ex)
                {
                    MessageBoxHelper.ShowError(ex);
                    return null;
                }
            }
            set
            {
                try
                {
                    lText.Text = value;

                    lText.Visible = !String.IsNullOrEmpty(value);

                    SetFormSize();

                    Refresh();
                }
                catch (Exception ex)
                {
                    MessageBoxHelper.ShowError(ex);
                }
            }
        }

        public static IProgressForm Create()
        {
            try
            {
                return new ProgressForm(null, null, null);
            }
            catch (Exception ex)
            {
                MessageBoxHelper.ShowError(ex);
                return null;
            }
        }

        public static void ShowProgress(string message, GuiHelper.ProcessActionDelegate progressAction, bool enableCancel, GuiHelper.ProcessActionDelegate2 cancelAction, object context)
        {
            ShowProgress(message, progressAction, null, enableCancel, cancelAction, context);
        }

        public static void ShowProgress(string message, GuiHelper.ProcessActionDelegate progressAction, GuiHelper.ProcessExceptionDelegate processException, bool enableCancel, GuiHelper.ProcessActionDelegate2 cancelAction, object context)
        {
            ProgressForm form = new ProgressForm(progressAction, processException, message, enableCancel, cancelAction, context);
            form.ShowDialog();

            if (form.Error != null)
            {
                throw new ProgressGuiException(form.Error, form.GetElapsed());
            }
        }
    }
}
